package gov.nih.nlm.lowlinc.core;

import gov.nih.nlm.lowlinc.accessionwrapper.LOINCID;
import gov.nih.nlm.lowlinc.accessionwrapper.LOINCPartID;
import gov.nih.nlm.lowlinc.accessionwrapper.LOINCTermID;
import gov.nih.nlm.lowlinc.bean.LOINCPart;
import gov.nih.nlm.lowlinc.bean.LOINCSystem2Part;
import gov.nih.nlm.lowlinc.bean.LOINCSystem3Part;
import gov.nih.nlm.lowlinc.csv.AxialGraph;
import gov.nih.nlm.lowlinc.csv.LOINCAxialCSVParser;
import gov.nih.nlm.lowlinc.csv.LOINCPartLinksCSVParser;
import gov.nih.nlm.lowlinc.csv.LOINCPartsCSVParser;
import gov.nih.nlm.lowlinc.owlapi.LowlincException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import org.apache.log4j.Logger;

public class MultiaxialComposer {
	private static final Logger log = Logger
			.getLogger(MultiaxialComposer.class);

	private static Integer cUnmatched = 0;
	private static Integer cMatched = 0;
	private static Integer cMultiple = 0;

	/**
	 * @param args
	 * @throws LowlincException
	 */
	public static void main(String[] args) throws LowlincException {
		// load
		AxialGraph axialGraph = new LOINCAxialCSVParser().getGraph();
		Map<LOINCPartID, LOINCPart> parts = new LOINCPartsCSVParser()
				.getParts();
		Map<LOINCTermID, List<LOINCPartID>> partLinks = new LOINCPartLinksCSVParser()
				.getLinks();
		// Set<LOINCTermBean> terms = new LOINCTermsCSVParser().getTerms();

		decomposeParts(axialGraph, parts, partLinks);

		// resolveXXX(parts);
	}

	private static void resolveXXX(Map<LOINCPartID, LOINCPart> parts) {

		Integer cStartsWith = 0;
		Integer cEquals = 0;
		Integer cContains = 0;
		Integer cMultipart = 0;

		for (LOINCPart part : parts.values()) {
			String label = part.getLabel().toLowerCase();
			if (label.equalsIgnoreCase("xxx")) {
				cEquals++;
				log.info("XXX EQUALS " + part);
			} else if (label.startsWith("xxx")) {
				cStartsWith++;
				log.debug("XXX STARTSWITH " + part);
			} else if (label.contains("xxx")) {
				cContains++;
				if (!label.contains("|")) {
					log.debug("XXX CONTAINS " + part);
				} else {
					cMultipart++;
				}
			}
		}

		// summary
		log.info("TOTAL XXX EQUALS " + cEquals);
		log.info("TOTAL XXX STARTSWITH " + cStartsWith);
		log.info("TOTAL XXX CONTAINS " + cContains);
		log.info("TOTAL XXX MULTIPARTS " + cMultipart);

	}

	public static void decomposeParts(AxialGraph axialGraph,
			Map<LOINCPartID, LOINCPart> parts,
			Map<LOINCTermID, List<LOINCPartID>> partLinks)
			throws LowlincException {

		log.info("post composing multiaxial parts");

		// create partNames
		Map<String, Set<LOINCPart>> partNames = createPartNames(parts);

		Map<String, Integer> histogram = new HashMap<>();

		Integer counter = 0;

		// iterate over all parts
		for (LOINCPartID partID : axialGraph.getParts()) {
			LOINCPart part = parts.get(partID);
			List<LOINCPart> partsComposition = new ArrayList<>();

			if (!(part.getPART_TYPE().equals("MULTI-AXIAL") || part
					.getPART_TYPE().equals("SUPERSYSTEMMULTI-AXIAL"))) {
				// primitive axial observation no need to de-compose
				// as it's essentially the part itself
				partsComposition.add(part);
				part.setPartComposition(partsComposition);
				continue;
			}
			counter++;

			processLexicalSubParts(part, partNames, partsComposition,
					axialGraph, partLinks);

			part.setPartComposition(partsComposition);

			// only a single part matched in a multiaxial
			// exclude multiaxials that are supersystems and start with ^
			// and multiaxials that had X X X
			if (partsComposition.size() == 1
					&& !part.getLabel().startsWith("^")) {
				log.warn("SINGLE AXIS MULTIAXIAL " + partsComposition + " "
						+ part);
			}

			// get the types for histogram
			StringBuilder decomposedTypes = new StringBuilder();
			for (LOINCPart mPart : partsComposition) {
				decomposedTypes.append(mPart.getPART_TYPE() + "\t");
			}
			Integer size = histogram.get(decomposedTypes.toString());
			if (size == null) {
				size = 0;
			}
			histogram.put(decomposedTypes.toString(), ++size);
		}

		log.info("Procesed multiaxial, supersystemmultiaxial " + counter);
		log.info("Matched subparts " + cMatched);
		log.info("Unmatched subparts " + cUnmatched);
		log.info("Multiple matched subparts " + cMultiple);
		Map<String, Integer> sortedMap = new TreeMap<String, Integer>(
				new ValueComparator<String>(histogram));
		sortedMap.putAll(histogram);
		for (Entry<String, Integer> e : sortedMap.entrySet()) {
			log.info(e.getValue() + "\t" + e.getKey());
		}
	}

	private static Map<String, Set<LOINCPart>> createPartNames(
			Map<LOINCPartID, LOINCPart> parts) {
		Map<String, Set<LOINCPart>> partNames = new HashMap<>();
		for (LOINCPart part : parts.values()) {
			if (!(part.getPART_TYPE().equals("COMPONENT")
					|| part.getPART_TYPE().equals("SCALE")
					|| part.getPART_TYPE().equals("TIME")
					|| part.getPART_TYPE().equals("METHOD")
					|| part.getPART_TYPE().equals("PROPERTY")
					|| part.getPART_TYPE().equals("SYSTEM") || part
					.getPART_TYPE().equals("SUPER SYSTEM"))) {
				continue;
			}

			// case name insensitive
			String partName = part.getLabel().toLowerCase();
			Set<LOINCPart> set = partNames.get(partName);
			if (set == null) {
				set = new HashSet<>();
				partNames.put(partName, set);
			}
			set.add(part);

			// display name
			partName = part.getDISPLAY_NAME().toLowerCase();
			set = partNames.get(partName);
			if (set == null) {
				set = new HashSet<>();
				partNames.put(partName, set);
			}
			set.add(part);
		}

		log.info("Unique part names " + partNames.size() + " Parts "
				+ parts.size());
		return partNames;
	}

	private static void processLexicalSubParts(LOINCPart part,
			Map<String, Set<LOINCPart>> partNames,
			Collection<LOINCPart> partsComposition, AxialGraph axialGraph,
			Map<LOINCTermID, List<LOINCPartID>> partLinks)
			throws LowlincException {

		LOINCPartID partID = part.getPartID();

		for (String subpart : part.getLabel().split(" \\| ")) {
			Boolean isSuperSystemExpected = false;
			// special supersystem case
			if (subpart.startsWith("^")) {
				isSuperSystemExpected = true;
				// drop the caret
				subpart = subpart.substring(1);
			}

			Set<LOINCPart> matchedParts = partNames.get(subpart.toLowerCase());

			// this is special case
			if (subpart.equalsIgnoreCase("Bld-Ser-Plas")
					&& matchedParts == null) {
				cMatched++;
				matchedParts = new HashSet<>();
				matchedParts.add(new LOINCSystem3Part());
			} else if (subpart.equalsIgnoreCase("drain & lines")
					&& matchedParts == null) {
				cMatched++;
				matchedParts = new HashSet<>();
				matchedParts.add(new LOINCSystem2Part());
			}
			// no parts matched
			else if (matchedParts == null) {
				log.info("UNMATCHED PART " + part.getPartID() + " " + "<"
						+ subpart + ">");
				cUnmatched++;

			} // that's what we want
			else if (matchedParts.size() == 1) {
				cMatched++;

			} // this is problematic
			else if (matchedParts.size() > 1) {
				Set<LOINCPartID> sharedParts = null;
				Set<LOINCPart> confirmedMatches = new HashSet<>();

				// find the supersystem among them
				if (isSuperSystemExpected) {
					for (LOINCPart matchedPart : matchedParts) {
						if (matchedPart.getPART_TYPE().equals("SUPER SYSTEM")) {
							confirmedMatches.add(matchedPart);
						}
					}
					if (confirmedMatches.size() > 1) {
						throw new LowlincException(
								"MULTIPLE SUPERSYSTEMS MATCHED IN " + part);
					}
				}

				// check all child terms of that multiaxial
				for (LOINCID loincid : axialGraph.getChildren(partID)) {
					if (loincid instanceof LOINCTermID) {
						// and what kind of parts they're linked to
						// adding them to matchedParts set
						if (sharedParts == null) {
							sharedParts = new HashSet<>(partLinks.get(loincid));
						}
						sharedParts.retainAll(partLinks.get(loincid));
					}
				}

				for (LOINCPart matchedPart : matchedParts) {
					if (sharedParts != null
							&& sharedParts.contains(matchedPart.getPartID())) {
						confirmedMatches.add(matchedPart);
					}
				}

				// we should have matched at least one
				if (confirmedMatches.size() == 0) {
					log.info("LOGICAL MISMATCH IN CHILDREN " + part + " "
							+ partID + " " + "<" + subpart + ">");
					log.info("\t" + matchedParts);
					confirmedMatches = matchedParts;
					cMatched++;
					// this is what we want
				} else if (confirmedMatches.size() == 1) {
					cMatched++;
					// this is problematic size>1
				} else {
					log.debug("MULTIPLE MATCHES " + partID + " "
							+ part.getLabel() + " <" + subpart + ">");
					log.debug(matchedParts);
					log.debug(confirmedMatches);
					cMultiple++;
				}
				matchedParts = confirmedMatches;
			}
			partsComposition.addAll(matchedParts);
		}
	}

	static class ValueComparator<T> implements Comparator<T> {
		Map<T, Integer> base;

		public ValueComparator(Map<T, Integer> histogram) {
			base = histogram;
		}

		@Override
		public int compare(T a, T b) {
			if (base.get(a) < base.get(b)) {
				return 1;
			} else if (base.get(a) == base.get(b)) {
				return 1;
			} else {
				return -1;
			}
		}
	}
}
